import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.models as models


class AtrousConv2d(nn.Module):
    """带孔卷积层的实现"""

    def __init__(self, in_channels, out_channels, kernel_size, dilation=1, padding=0):
        super(AtrousConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size,
                              padding=padding, dilation=dilation)

    def forward(self, x):
        return self.conv(x)


class ASPP(nn.Module):
    """Atrous Spatial Pyramid Pooling"""

    def __init__(self, in_channels, out_channels):
        super(ASPP, self).__init__()

        # 使用不同的膨胀率的带孔卷积
        self.conv_1x1 = nn.Conv2d(in_channels, out_channels, kernel_size=1)
        self.conv_3x3_1 = AtrousConv2d(in_channels, out_channels, kernel_size=3,
                                       dilation=6, padding=6)
        self.conv_3x3_2 = AtrousConv2d(in_channels, out_channels, kernel_size=3,
                                       dilation=12, padding=12)
        self.conv_3x3_3 = AtrousConv2d(in_channels, out_channels, kernel_size=3,
                                       dilation=18, padding=18)

        self.global_avg_pool = nn.Sequential(
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Conv2d(in_channels, out_channels, 1, bias=False),
            nn.ReLU()
        )

        self.output = nn.Sequential(
            nn.Conv2d(out_channels * 5, out_channels, 1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU()
        )

    def forward(self, x):
        size = x.size()[2:]

        conv_1x1 = self.conv_1x1(x)
        conv_3x3_1 = self.conv_3x3_1(x)
        conv_3x3_2 = self.conv_3x3_2(x)
        conv_3x3_3 = self.conv_3x3_3(x)

        global_features = self.global_avg_pool(x)
        global_features = F.interpolate(global_features, size=size,
                                        mode='bilinear', align_corners=True)

        concat_features = torch.cat([conv_1x1, conv_3x3_1, conv_3x3_2,
                                     conv_3x3_3, global_features], dim=1)

        return self.output(concat_features)


class DeepLab(nn.Module):
    def __init__(self, num_classes):
        super(DeepLab, self).__init__()

        # 使用预训练的ResNet作为backbone
        resnet = models.resnet101()
        self.layer0 = nn.Sequential(
            resnet.conv1,
            resnet.bn1,
            resnet.relu,
            resnet.maxpool
        )
        self.layer1 = resnet.layer1
        self.layer2 = resnet.layer2
        self.layer3 = resnet.layer3
        self.layer4 = resnet.layer4

        # ASPP模块
        self.aspp = ASPP(2048, 256)

        # Low-level features的处理
        self.low_level_conv = nn.Sequential(
            nn.Conv2d(256, 48, 1, bias=False),  # 将channel数降到48
            nn.BatchNorm2d(48),
            nn.ReLU()
        )

        # 解码器部分
        self.decoder = nn.Sequential(
            nn.Conv2d(304, 256, 3, padding=1, bias=False),  # 304 = 256 + 48
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, 256, 3, padding=1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.Conv2d(256, num_classes, 1)
        )

    def forward(self, x):
        input_size = x.size()[2:]

        # Encoder
        x = self.layer0(x)
        x = self.layer1(x)
        low_level_feat = x  # shape: [N, 256, H/4, W/4]

        # 处理low-level features
        low_level_feat = self.low_level_conv(low_level_feat)

        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        # ASPP
        x = self.aspp(x)

        # 上采样ASPP输出
        x = F.interpolate(x, size=low_level_feat.shape[2:],
                          mode='bilinear', align_corners=True)

        # 特征融合
        x = torch.cat([x, low_level_feat], dim=1)

        # Decoder
        x = self.decoder(x)

        # 最终上采样到原始图像大小
        output = F.interpolate(x, size=input_size, mode='bilinear', align_corners=True)
        return output


# 使用示例
if __name__ == '__main__':
    # 创建模型
    x = torch.rand(1, 3, 224, 224)
    model = DeepLab(1)
    pred = model(x)
    print(pred.shape)
